package edu.hawaii.senin.iclouds.plotting;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import edu.hawaii.senin.iclouds.util.TextUtils;

/**
 * 
 * Parses iClouds evolution log and makes internal data structures that corresponds to log. Could be
 * queried for the data fond.
 * 
 * @author Pavel Senin
 * 
 */
public class EvolutionLogParser {

  /* the population-related variables */
  private final Pattern accretionEventPattern = Pattern
      .compile("case_1:\\saccretion\\sevent:\\s[\\d]*");
  private final Pattern cascadeEventPattern = Pattern
      .compile("case_2:\\saccretion\\sevent:\\s[\\d]*");
  // private final Pattern cascadeEventPattern = Pattern.compile("Accretion\\sevent:\\s[\\d]*,");

  // private final Pattern grainPopulationSizePattern = Pattern
  // .compile("grain\\spopSize:\\s[\\d]*\\s");
  private final Pattern activePopulationPattern = Pattern.compile("surface:[\\w\\s]*\\sburied:");
  private final Pattern buriedPopulationPattern = Pattern.compile("buried:\\s");

  private TreeMap<Long, TreeMap<String, Long>> activePopulation;
  private TreeMap<Long, TreeMap<String, Long>> buriedPopulation;
  private String kind;
  private String logFormat;

  /**
   * Instantiates all storages.
   * 
   * @param kind our subject of interest, evolution or cascade
   * @param format logger format, xml or txt.
   */
  public EvolutionLogParser(String kind, String format) {
    this.kind = kind;
    this.logFormat = format;
    activePopulation = new TreeMap<Long, TreeMap<String, Long>>();
    buriedPopulation = new TreeMap<Long, TreeMap<String, Long>>();
  }

  /**
   * Does actual parsing.
   * 
   * @param fileName log file name.
   * @throws NumberFormatException if something wrong with conversion.
   * @throws IOException when IO error encountered.
   */
  public void parse(String fileName) throws NumberFormatException, IOException {

    this.activePopulation = new TreeMap<Long, TreeMap<String, Long>>();
    this.buriedPopulation = new TreeMap<Long, TreeMap<String, Long>>();

    if ("xml".equalsIgnoreCase(this.logFormat)) {

      SAXBuilder builder = new SAXBuilder();
      BufferedReader reader = new BufferedReader(new FileReader(new File(fileName)));

      Document config;
      try {
        config = builder.build(reader);
        Element root = config.getRootElement();

        Iterator itr = root.getChildren().iterator();
        while (itr.hasNext()) {
          Element el = (Element) itr.next();
          String message = el.getChildText("message");

          Matcher m = null;
          String tmp = null;
          Long accretionEvent = null;

          if ("evolution".equalsIgnoreCase(this.kind)) {
            m = accretionEventPattern.matcher(message);
            if (m.find()) {
              tmp = message.substring(m.start(), m.end());
              accretionEvent = Long.valueOf(tmp.substring("case_1: accretion event: ".length(), tmp
                  .length()));
            }
          }
          else if ("cascade".equalsIgnoreCase(this.kind)) {
            m = cascadeEventPattern.matcher(message);
            if (m.find()) {
              tmp = message.substring(m.start(), m.end());
              accretionEvent = Long.valueOf(tmp.substring("case_2: accretion event: ".length(), tmp
                  .length()));
              while (this.activePopulation.containsKey(accretionEvent)) {
                accretionEvent++;
              }
            }
          }
          if (null != m) {
            m = activePopulationPattern.matcher(message);
            if (m.find()) {
              tmp = message.substring(m.start() + "surface: ".length(), m.end()
                  - " buried:".length());
              TreeMap<String, Long> tmpPop = new TreeMap<String, Long>();
              String[] breakedLine = tmp.split("\\s+");
              if (breakedLine.length >= 2) {
                for (int i = 0; i < breakedLine.length; i += 2) {
                  String specieName = TextUtils.chomp(breakedLine[i]);
                  Long numValue = Long.valueOf(TextUtils.chomp(breakedLine[i + 1]));
                  tmpPop.put(specieName, numValue);
                }
              }
              activePopulation.put(accretionEvent, tmpPop);
            }

            m = buriedPopulationPattern.matcher(message);
            if (m.find()) {
              tmp = message.substring(m.start() + "buried: ".length());
              TreeMap<String, Long> tmpPop = new TreeMap<String, Long>();
              String[] breakedLine = tmp.split("\\s+");
              if (breakedLine.length >= 2) {
                for (int i = 0; i < breakedLine.length; i += 2) {
                  String specieName = TextUtils.chomp(breakedLine[i]);
                  Long numValue = Long.valueOf(TextUtils.chomp(breakedLine[i + 1]));
                  tmpPop.put(specieName, numValue);
                }
              }
              buriedPopulation.put(accretionEvent, tmpPop);
            }
          }
        }
        reader.close();
      }
      catch (JDOMException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    else {
      BufferedReader in = new BufferedReader(new FileReader(new File(fileName)));

      String cLine = null;
      while ((cLine = in.readLine()) != null) {
        TextUtils.chomp(cLine);

        Matcher m = null;
        String tmp = null;
        Long accretionEvent = 0L;

        if ("evolution".equalsIgnoreCase(this.kind)) {
          m = accretionEventPattern.matcher(cLine);
          if (m.find()) {
            tmp = cLine.substring(m.start(), m.end());
            accretionEvent = Long.valueOf(tmp.substring("case_1: accretion event: ".length(), tmp
                .length()));
          }
        }
        else if ("cascade".equalsIgnoreCase(this.kind)) {
          m = cascadeEventPattern.matcher(cLine);
          if (m.find()) {
            tmp = cLine.substring(m.start(), m.end());
            accretionEvent = Long.valueOf(tmp.substring("case_2: accretion event: ".length(), tmp
                .length()));
            while (this.activePopulation.containsKey(accretionEvent)) {
              accretionEvent += 1;
            }
          }
        }

        if (null != m) {
          m = activePopulationPattern.matcher(cLine);
          if (m.find()) {
            tmp = cLine.substring(m.start() + "surface: ".length(), m.end() - " buried:".length());
            TreeMap<String, Long> tmpPop = new TreeMap<String, Long>();
            String[] breakedLine = tmp.split("\\s+");
            if (breakedLine.length >= 2) {
              for (int i = 0; i < breakedLine.length; i += 2) {
                String specieName = TextUtils.chomp(breakedLine[i]);
                Long numValue = Long.valueOf(TextUtils.chomp(breakedLine[i + 1]));
                tmpPop.put(specieName, numValue);
              }
            }
            // System.out.println(accretionEvent + " " + tmpPop.toString());
            activePopulation.put(accretionEvent, tmpPop);
          }

          m = buriedPopulationPattern.matcher(cLine);
          if (m.find()) {
            tmp = cLine.substring(m.start() + "buried: ".length());
            TreeMap<String, Long> tmpPop = new TreeMap<String, Long>();
            String[] breakedLine = tmp.split("\\s+");
            if (breakedLine.length >= 2) {
              for (int i = 0; i < breakedLine.length; i += 2) {
                String specieName = TextUtils.chomp(breakedLine[i]);
                Long numValue = Long.valueOf(TextUtils.chomp(breakedLine[i + 1]));
                tmpPop.put(specieName, numValue);
              }
            }
            buriedPopulation.put(accretionEvent, tmpPop);
          }
        }
      }
      in.close();
    }
  }

  /**
   * Reports active grain population as the result of log parsing.
   * 
   * @return active on-surface population.
   */
  public TreeMap<Long, TreeMap<String, Long>> getActivePopulation() {
    return this.activePopulation;
  }

  /**
   * Reports buried grain population as the result of log parsing.
   * 
   * @return buried grain population.
   */
  public TreeMap<Long, TreeMap<String, Long>> getBuriedPopulation() {
    return this.buriedPopulation;
  }

}
